查看原文
其他

树变图,图变树?

2017-01-02 Y叔 biobabble

总有人会问我plot和ggplot2有什么差别?这个问题很难回答,plot和ggplot2应该说是不可比较的,因为不在一个level上。


我们先来看plot的函数定义:

> plot
function (x, y, ...)
UseMethod("plot")
<bytecode: 0x1843198>
<environment: namespace:graphics>


这是个S3方法,plot还有S4方法,在stats4里定义:

> stats4::plot
standardGeneric for "plot" defined from package "graphics"

function (x, y, ...)
standardGeneric("plot")
<environment: 0x1d7b058>
Methods may be defined for arguments: x, y
Use  showMethods("plot")  for currently available ones.


我们在R里对各种对象用plot(x),它画出来图形来,不同的对象会出来不同的图,因为它会根据对象的类调用相应的方法,与C++/JAVA等class-based OO不同,R是method-based的,所以不是x.plot(),而是plot(x),但都是通过不同的对象调用不同的方法。plot本身的定义是个generic method,不同的对象如何来画图,需要有相应的plot方法实现。


打开R,键入plot,按下Tab键,你可以看到:

> plot
plot                 plot.function        plot.stepfun
plot.default         plot.new             plot.ts
plot.design          plot.spec.coherency  plot.window
plot.ecdf            plot.spec.phase      plot.xy

随着R的启动,graphics包自带几个plot方法,更多的plot方法由其它的R包提供,比较ape里有plot.phylo,如果一个对象没有相应的plot方法,那么plot(x)通常就会画不出来(最终会调用plot.default,可能可以画出散点)。

比如:

> x = data.frame(a = 1:4, b=1:4)
> plot(x)

将画出散点图,但如果设置class属性,同样的数据,没法画出来:

> class(x) = "test"
> plot(x)
Error in xy.coords(x, y, xlabel, ylabel, log) :
  'x' is a list, but does not have components 'x' and 'y'


plot只是个generic,它可以通过传入的对象调用相应的方法(在有相应实现的情况下),在ggtree里我也定义了几个plot方法,不过在开发版本里已经被我删掉了,觉得没有必要性。这里可以看出来plot方法也可以是基于ggplot2的,方法如何实现,完全是自由的,你可以基于base graphics,也可以基于grid,可以用ggplot2,也可以用lattice。


所以说plot和ggplot2是不可比较的,一个是方法(也就是函数),一个是画图系统,根本不在一个可比较的level上。


你看我emojifont包的描述是An implementation of using emoji and fontawesome for using in both base and 'ggplot2' graphics.


这里我写的是base and ggplot2 graphics,提问者想要问的其实是base graphics和ggplot2的比较。


base图形系统是个传统的画图系统,也就是在画图设备上添砖加瓦,不断加上各种线条和点,好比给你纸笔,但没有橡皮擦,这是个dead end,出来就是个图片。


R画图一个划时代的分水岭是grid包,这是Paul Murrell在读phd的时候开发的,从此画图产生的不再是图片,而是对象,在grid里图形对象叫grob,代表graphic object,这个对象和其它R里的对象一样,是数据。这个数据可以在用户的需求之下渲染成图片。lattice和ggplot2都是基于grid,所以base和ggplot2的差别之一,也就是base和grid的差别,base系统就是画图,而grid的图是可操作的对象,不再是dead end。


ggplot是Hadley Wickham在读phd的时候开发的,后来推倒重来,出来ggplot2。和其它画图系统不同的是,ggplot2画图不再是纸笔模式,画图不再是点线组合,而是各种图形组合,这让我们可以在数据层面上思考画图,而不再是这里画点,那里又画线,给这条线上什么颜色,我们不用考虑这些细节,而是按不同的变量给我上色之类的高级抽象。这得益于《grammar of graphics》,他提出了一套图形语法,让我们在考虑构建一幅图形的时候,不用掉进细节的坑里,而是把图形拆分成可以自由组合的成分。Hadley Wickham在R里通过把这套想法给实现了,于是我们有了ggplot2,画图基本上就是+geom,不断加图层,让我们在更高的抽象水平思考问题。


(铺垫完了)Paul Murrell读phd写了grid,Hadley Wickham读phd写了ggplot2,Y叔读phd写了ggtree,把自己和神人们放在一起,似乎没毛病,lol...


得益于grid系统,图本身是数据,在需要的时候,它可以渲染成图片,但它本身是数据。在ggtree里面,我们把进化树画出来,可以加各种注释,图本身就是数据!使用ggtree我们可以画树,还可以把图变成树,有树的拓扑结构,还可以有树的注释信息。


> require(ggtree)
> nhxfile <- system.file("extdata/NHX", "ADH.nhx", package="treeio")
> nhx <- read.nhx(nhxfile)
> p = ggtree(nhx)
> as.phylo(p)

Phylogenetic tree with 8 tips and 4 internal nodes.

Tip labels:
    ADH2, ADH1, ADHY, ADHX, ADH4, ADH3, ...

Rooted; includes branch lengths.


我们用ggtree(nhx)画树,可以使用as.phylo把图变回树,包含了树的拓扑结构。


我们还可以用as.treedata把图中的拓扑结构和注释信息一同存入于树对象中:


> as.treedata(p)
'treedata' S4 object that stored information of
     ''.

...@ tree:
Phylogenetic tree with 8 tips and 4 internal nodes.

Tip labels:
    ADH2, ADH1, ADHY, ADHX, ADH4, ADH3, ...

Rooted; includes branch lengths.

with the following features available:
     'S',    'D',    'B'.


更为牛逼的是ggtree定义%<+%操作符,可以把用户自己的数据加到图上加注释,as.treedata可以把这些额外添加的数据也保存于树对象中。


> d = data.frame(label=as.phylo(nhx)$tip.label, trait=abs(rnorm(8)))
> p = p %<+% d
> as.treedata(p)
'treedata' S4 object that stored information of
     ''.

...@ tree:
Phylogenetic tree with 8 tips and 4 internal nodes.

Tip labels:
    ADH2, ADH1, ADHY, ADHX, ADH4, ADH3, ...

Rooted; includes branch lengths.

with the following features available:
     'S',    'D',    'B',    'trait'.


我们把trait加到图上,再转为树,于是树对象中多了trait变量。


x <- as.treedata(p)

ggtree(x) + geom_tiplab(align=T, offset=.005) + geom_tippoint(aes(size=trait)) + xlim(NA, 0.28) + geom_label(aes(x=branch, label=S))


把图p变回树x,额外加进去的trait,也被存在树中,可以拿来重新画在图上。



树变图,图变树,反反复复,无限嵌套。

self = function(x) self(ggtree(as.phylo(x)))


就这么简单一行,就是个无限的递归,必定跑不出来:

> self(rtree(30))
Error: evaluation nested too deeply: infinite recursion / options(expressions=)?


套路太深。


假如你的树丢了,图有保存,你还能够取回树,可以写newick或nexus文件出来。ggtree在手,世界我有!


PS: as.phylo和as.treedata方法都定义于treeio包中,你值得拥有!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存